package com.atguigu.gmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.pojo.Goods;
import com.atguigu.gmall.search.pojo.SearchParamVo;
import com.atguigu.gmall.search.pojo.SearchResponseAttrVo;
import com.atguigu.gmall.search.pojo.SearchResponseVo;
import com.atguigu.gmall.search.service.SearchService;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.checkerframework.checker.units.qual.C;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import springfox.documentation.spring.web.json.Json;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Jia Fa
 * @create 2021-12-07 13:57
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    RestHighLevelClient client;

    @Override
    public SearchResponseVo querySearch(SearchParamVo paramVo) {
        try {
            //查询条件构建
            SearchResponse response = this.client.search(new SearchRequest(new String[]{"goods"}, buildDSl(paramVo)), RequestOptions.DEFAULT);
            //解析结果集
            SearchResponseVo searchResponseVo = parseResult(response);
            // 设置分页参数 response结果集中没有这两个参数
            searchResponseVo.setPageNum(paramVo.getPageNum());
            searchResponseVo.setPageSize(paramVo.getPageSize());

            return searchResponseVo;

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 解析搜索结果集  将response-->  SearchResponseVo
     *
     * @param response
     * @return
     */
    private SearchResponseVo parseResult(SearchResponse response) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();

        SearchHits hits = response.getHits();
        //解析搜索结果集    设置总记录数  3
        searchResponseVo.setTotal(hits.getTotalHits().value);  // 注意 getTotalHits里面有两个参数（value、relation）,需指明具体的
        //获取原生结果集
        SearchHit[] hitsHits = hits.getHits();
        //解析结果集，转化为流--转为goods集合
        if (hitsHits != null && hitsHits.length > 0) {
            // hitsHits是数组，所以采用Stream.of 的方式获取流
            searchResponseVo.setGoodsList(Stream.of(hitsHits).map(hit -> {
                //在流中直接将json转为goods对象
                String json = hit.getSourceAsString();
                // 转为goods对象  此时是原生对象 需要设置高亮属性
                Goods goods = JSON.parseObject(json, Goods.class);

                //获取高亮结果集
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                //值一个隐含数组 内部仅一元素
                HighlightField highlightField = highlightFields.get("title");
                String title = highlightField.getFragments()[0].string();
                //设置高亮字段    4
                goods.setTitle(title);

                return goods;
            }).collect(Collectors.toList()));
        }


        // 解析聚合结果集
        Aggregations aggregations = response.getAggregations();

        // 2.1 商品聚合结果集  商品Id聚合桶 说明桶是Id类的  Long   注意桶内桶
        ParsedLongTerms brandIdAgg = aggregations.get("brandIdAgg");
        //获取商品聚合结果桶
        List<? extends Terms.Bucket> buckets = brandIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(buckets)) {
            // 将桶集合转为Brands集合
            List<BrandEntity> brandList = buckets.stream().map(bucket -> {
                BrandEntity brandEntity = new BrandEntity();
                // ---------设置品牌Id属性------------
                brandEntity.setId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());

                // 获取桶内的所有子聚合
                Aggregations subAggregations = ((Terms.Bucket) bucket).getAggregations();

                // 获取 brandNameAgg 子聚合   子桶1
                ParsedStringTerms nameAgg = subAggregations.get("brandNameAgg");
                List<? extends Terms.Bucket> nameAggBuckets = nameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(nameAggBuckets)) {
                    //将集合内部第一个元素设置给brandName
                    // ---------设置品牌name属性------------
                    brandEntity.setName(nameAggBuckets.get(0).getKeyAsString());
                }

                // 获取 logAgg 子聚合   子桶2
                ParsedStringTerms logAgg = subAggregations.get("logAgg");
                List<? extends Terms.Bucket> logAggBuckets = logAgg.getBuckets();
                if (!CollectionUtils.isEmpty(logAggBuckets)) {
                    // ---------设置品牌logo属性------------
                    brandEntity.setLogo(logAggBuckets.get(0).getKeyAsString());
                }
                return brandEntity;
            }).collect(Collectors.toList());
            // 5 设置品牌属性
            searchResponseVo.setBrands(brandList);

        }

        // 2.2 分类聚合结果集  注意桶内桶
        ParsedLongTerms categoryIdAgg = aggregations.get("categoryIdAgg");
        List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(categoryIdAggBuckets)) {
            //设置分类属性  集合转换为分类集合对象CategoryEntity
            searchResponseVo.setCategories(categoryIdAggBuckets.stream().map(CidBucket -> {
                CategoryEntity categoryEntity = new CategoryEntity();
                // ---------设置分类id属性------------
                categoryEntity.setId(((Terms.Bucket) CidBucket).getKeyAsNumber().longValue());
                //获取所有子桶           子桶1
                Aggregations categoryNameAgg = ((Terms.Bucket) CidBucket).getAggregations();
                ParsedStringTerms categoryName = categoryNameAgg.get("categoryNameAgg");
                List<? extends Terms.Bucket> categoryNameBuckets = categoryName.getBuckets();
                if (!CollectionUtils.isEmpty(categoryNameBuckets)) {
                    // 设置categoryName字段  获取集合（桶）的第一个元素的key值
                    // ---------设置分类name属性------------
                    categoryEntity.setName(categoryNameBuckets.get(0).getKeyAsString());
                }

                return categoryEntity;
            }).collect(Collectors.toList()));

        }
        // 2.3 规格参数嵌套聚合结果集
        // 获取嵌套聚合 ================= ParsedNested ==============  attrAgg--> attrIdAgg-->attrName(attrValue)
        ParsedNested attrAgg = aggregations.get("attrAgg");
        // 获取嵌套子聚合  attrIdAgg
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");


        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        // 进行集合转换
        if (!CollectionUtils.isEmpty(attrIdAggBuckets)) {

            searchResponseVo.setFilters(attrIdAggBuckets.stream().map(attrAggBucket -> {
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
                // =====================设置规格参数id=====================
                searchResponseAttrVo.setAttrId(((Terms.Bucket) attrAggBucket).getKeyAsNumber().longValue());
                //  获取所有子聚合
                Aggregations subAggregations = ((Terms.Bucket) attrAggBucket).getAggregations();

                // 获取attrName子聚合
                ParsedStringTerms attrNameAgg = subAggregations.get("attrName");
                List<? extends Terms.Bucket> attrNameAggBuckets = attrNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(attrNameAggBuckets)) {
                    // 将Name桶内的key取出设置给Vo对象  由于根据id查询到的名字仅一个  故桶内仅一元素
                    //======================设置规格参数attrName==============================
                    searchResponseAttrVo.setAttrName(attrNameAggBuckets.get(0).getKeyAsString());
                }

                // 获取attrValue聚合 将桶内的value数据集转为String集合List<String> attrValues;
                ParsedStringTerms attrValueAgg = subAggregations.get("attrValue");
                List<? extends Terms.Bucket> valueAggBuckets = attrValueAgg.getBuckets();
                if (!CollectionUtils.isEmpty(valueAggBuckets)) {
                    //======================设置规格参数attrValue==============================
                    searchResponseAttrVo.setAttrValues(
                            // 流转换 获取字符串集合 searchResponseAttrVo.setAttrValues需要一个字符串集合的参数
                            valueAggBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()));
                }


                return searchResponseAttrVo;
            }).collect(Collectors.toList()));


        }


        return searchResponseVo;
    }

    /**
     * 接收页面传递参数解析 构建查询DSL语句
     *
     * @param paramVo
     * @return
     */
    private SearchSourceBuilder buildDSl(SearchParamVo paramVo) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 判断是否存在搜索条件
        String keyword = paramVo.getKeyword();
        if (keyword == null) {
            throw new RuntimeException("暂无该商品");
        }

        // 1 构建查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        searchSourceBuilder.query(boolQueryBuilder);
        // 1 匹配查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));
        //2 过滤
        //2.1 品牌过滤
        List<Long> brandId = paramVo.getBrandId();
        if (!CollectionUtils.isEmpty(brandId)) {

            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandId));
        }

        // 2.2 分类过滤
        List<Long> cid = paramVo.getCid();
        if (!CollectionUtils.isEmpty(cid)) {

            boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId", cid));
        }
        // 2.3 库存过滤
        Boolean store = paramVo.getStore();
        if (store != null) {
            // 由于库存均为false，所以此处传递一个store 正常应为True
            boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));
        }
        // 2.4 价格区间过滤
        Double priceFrom = paramVo.getPriceFrom();
        Double priceTo = paramVo.getPriceTo();
        // 存在起始价格或者终结价格
        if (priceFrom != null || priceTo != null) {
            //获取区间过滤对象
            RangeQueryBuilder range = QueryBuilders.rangeQuery("price");
            if (priceFrom != null) {
                range.gte(priceFrom);
            }
            if (priceTo != null) {
                range.lte(priceTo);
            }
            boolQueryBuilder.filter(range);
        }
        // 2.5. 规格参数的过滤 props= 5:8G-12G&props=6:骁龙865-硅谷1000
        List<String> props = paramVo.getProps();
        if (!CollectionUtils.isEmpty(props)) {
            props.forEach(prop -> { // 5:8G-12G
                String[] attrs = StringUtils.split(prop, ":");
                //判断过滤参数是否为空 长度为2  首位是数字  attrId
                if (attrs != null && attrs.length == 2 && NumberUtils.isCreatable(attrs[0])) {
                    //boolQuery的原因是为了进行内层的must或者filter查询
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    // 注意 这是个嵌套查询attrList
                    //设置嵌套内部查询  设置规格参数id
                    boolQuery.must(QueryBuilders.termQuery("attrList.attrId", attrs[0]));
                    // 获得规格参数值属性
                    String[] attrValues = StringUtils.split(attrs[1], "-");
                    boolQuery.must(QueryBuilders.termsQuery("attrList.attrValue", attrValues));
                    //QueryBuilders.nestedQuery 是一个嵌套查询 path路径 boolQuery的原因是为了进行内层的must及filter查询 ScoreMode.None默认的
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrList", boolQuery, ScoreMode.None));

                }
            });
        }

        //3 排序  尝试是否能以一个变量直接传入
        // 排序字段：0-默认，得分降序；1-按价格升序；2-按价格降序；3-按创建时间降序；4-按销量降序
        Integer sort = paramVo.getSort();
        switch (sort) {
            case 1:
                searchSourceBuilder.sort("price", SortOrder.DESC);
                break;
            case 2:
                searchSourceBuilder.sort("price", SortOrder.ASC);
                break;
            case 3:
                searchSourceBuilder.sort("createTime", SortOrder.DESC);
                break;
            case 4:
                searchSourceBuilder.sort("sales", SortOrder.DESC);
                break;
            default:
                searchSourceBuilder.sort("_score", SortOrder.DESC);
        }
        //4 高亮
        searchSourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<em>").postTags("</em>"));
        //5 分页
        Integer pageNUm = paramVo.getPageNum();
        Integer pageSize = paramVo.getPageSize();

        searchSourceBuilder.from((pageNUm - 1) * pageSize); // 从哪条开始
        searchSourceBuilder.size(pageSize);// 每页条数
        //6 聚合
        // 6.1  品牌聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId")
                .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                .subAggregation(AggregationBuilders.terms("logAgg").field("logo")));
        // 6.2  分类聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("categoryIdAgg").field("categoryId")
                        .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
        // 6.3  规格参数嵌套聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrList")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrList.attrId")
                        .subAggregation(AggregationBuilders.terms("attrName").field("attrList.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValue").field("attrList.attrValue"))));
        // 7. 构建结果集过滤  加上这个会少很多字段 日了狗了 查了半天
        searchSourceBuilder.fetchSource(new String[]{"skuId", "title","subtitle", "price", "defaultImage"}, null);

        System.out.println("DSL语句为 = " + searchSourceBuilder);
        return searchSourceBuilder;
    }
}
